import os
import arch

vars = Variables()
vars.AddVariables(
  EnumVariable( 'example', 'Example that should be compiled', 'minimal',
                allowed_values=('hosvd', 'matmul', 'minimal', 'seissol_eqspp', 'springer', 'stock', 'tce', 'optimal_ind')),
  EnumVariable( 'compileMode', 'Mode of the compilation', 'release',
                allowed_values=('debug', 'release')),
  EnumVariable( 'variant', 'Example-specific variant.', '', allowed_values=('', 'onlyblas')),
  EnumVariable( 'arch',
                'Precision and architecture.',
                'dsnb',
                allowed_values=arch.getArchitectures()
              ),
  BoolVariable( 'unitTests', 'Build unit tests', False ),
)

# set environment
env = Environment(variables=vars)
env['ENV'] = os.environ
if 'CC' in env['ENV']:
  env['CC'] = env['ENV']['CC']
if 'CXX' in env['ENV']:
  env['CXX'] = env['ENV']['CXX']

# generate help text
Help(vars.GenerateHelpText(env))
  
# handle unknown, maybe misspelled variables
unknownVariables = vars.UnknownVariables()

# exit in the case of unknown variables
if unknownVariables:
  raise EnvironmentError("*** The following build variables are unknown: " + str(unknownVariables.keys()))

# c++ standard
env.Append(CXXFLAGS=['-std=c++11'])

# include directories
env.Append( CPPPATH=['#/../include/', env['example'], 'common'] )

# Architecture dependent settings
archFlags = arch.getFlags(env['arch'])
env.Append( CXXFLAGS  = archFlags,
            LINKFLAGS = archFlags )
env.Append(CPPDEFINES=arch.getDefines(env['arch']))

# Compiler optimisation settings
if env['compileMode'] == 'release':
  optFlags = ['-O2']
  env.Append(CPPDEFINES='NDEBUG')
else:
  optFlags = ['-O0']

# Link to BLAS
if env['example'] in ['hosvd', 'matmul', 'springer', 'stock', 'tce', 'optimal_ind']:
  env.Append(LIBS=['mkl_intel_lp64', 'mkl_sequential', 'mkl_core'])
  mklroot = env['ENV']['MKLROOT']
  if mklroot:
    env.Append(LIBPATH=os.path.join(mklroot, 'lib', 'intel64'))
    env.Append(CPPPATH=os.path.join(mklroot, 'include'))

env.Append( CXXFLAGS  = optFlags,
            LINKFLAGS = optFlags )

variantSuffix = '_' + env['variant'] if env['variant'] else ''
genCodeDir = os.path.join(env['example'], env['arch'] + variantSuffix)
addOutDir = lambda it: [os.path.join(genCodeDir, i) for i in it]

sources = addOutDir(['kernel.cpp', 'init.cpp', 'tensor.cpp', 'trashTheCache.cpp'])
sourcesNoRedZone = addOutDir(['subroutine.cpp'])
headers = addOutDir(['kernel.h', 'init.h', 'tensor.h', 'subroutine.h'])
headersUnitTests = addOutDir(['KernelTest.t.h'])

variantArg = '--variant ' + env['variant'] if env['variant'] else ''
env.Append(BUILDERS = {'Generate': Builder(generator=lambda target, source, env, for_signature: './example.py --arch {} {} {}'.format(env['arch'], variantArg, env['example']))})
generated = env.Generate(sources + sourcesNoRedZone + headers + headersUnitTests,
                         ['example.py', env['example'] + '.py'])

objects = [env.Object(source) for source in sources]
# Compile with -mno-red-zone due to bug in libxsmm
objectsNoRedZone = [env.Object(source, CXXFLAGS=env['CXXFLAGS'] + '-mno-red-zone') for source in sourcesNoRedZone]

mainObj = env.Object(os.path.join(genCodeDir, 'performance.cpp'))
env.Program(os.path.join(env['example'], 'performance_' + env['arch'] + variantSuffix), objects + objectsNoRedZone + [mainObj])

if env['unitTests']:
  env.Tool('cxxtest')
  env.CxxTest(os.path.join(env['example'], 'unit_tests_' + env['arch'] + variantSuffix), headersUnitTests + objects + objectsNoRedZone)
